#include "stdafx.h"
#include "textParser.h"
#include "textParserDefinitions.h"
#include "textTokens.h"
#include "layoutDesc.h"
#include "layoutParser.h"
#include "layoutBuilder.h"

#define TOKEN_STRUCT    310
#define TOKEN_MESSAGE	311
#define TOKEN_FIELD		312
#define TOKEN_ARRAY		313
#define TOKEN_OPTIONAL	314
#define TOKEN_INPLACE	317

class CLayoutScriptTokens : public CTextParserDefinitions
{
public:
	CLayoutScriptTokens()
	{
		AddChar( '{' );
		AddChar( '}' );
		AddChar( '=' );
		AddChar( ';' );
		AddChar( ':' );
		
		m_tokenString = TOKEN_STRING;
		m_tokenFloat = TOKEN_NUMBER;
		m_tokenInteger = TOKEN_NUMBER;
		m_tokenIdent = TOKEN_IDENT;

		AddKeyword( "$struct", TOKEN_STRUCT );
		AddKeyword( "$message", TOKEN_MESSAGE );
		AddKeyword( "$field", TOKEN_FIELD );
		AddKeyword( "$optional", TOKEN_OPTIONAL );
		AddKeyword( "$array", TOKEN_ARRAY );
		AddKeyword( "$inplace", TOKEN_INPLACE );		
	}
};

class CLayoutBuilderTokens : public CLayoutBuilder::IErrorReporter
{
public:
	CLayoutBuilderTokens( CTokenStream& tokenStream )
		: m_stream( &tokenStream )
		, m_numErrors( 0 )
	{}

	inline int GetNumErrors() const
	{
		return m_numErrors;
	}

	inline const CToken* Get()
	{
		const CToken* ret = m_stream->Get();
		SetContext( *ret );
		return ret;
	}

	const CToken* Peek()
	{
		return m_stream->Peek();
	}

	const bool End() const
	{
		return m_stream->End();
	}

	virtual void EmitError( const char* message ) override final
	{
		fprintf( stderr, "%s(%d): error: %s\n", m_contextFile.c_str(), m_contextLine, message );
		m_numErrors += 1;
	}

	bool MatchToken( const int id )
	{
		if ( Peek()->m_id == id )
		{
			Get();
			return true;
		}

		return false;
	}

	const CToken* MatchRequiredToken( const int id, std::string* retText = nullptr )
	{
		const auto* ret = Get();
		if ( ret->m_id == id )
		{
			if ( retText )
			{
				*retText = ret->m_text;
			}
			return ret;
		}

		fprintf( stderr, "%s(%d): error: Unexpected '%s'\n", m_contextFile.c_str(), m_contextLine, ret->m_text.c_str() );
		return nullptr;
	}

	void UnexpectedToken()
	{
		const auto cur = Peek();
		if ( cur )
			fprintf( stderr, "%s(%d): error: Unexpected '%s'\n", m_contextFile.c_str(), m_contextLine, cur->m_text.c_str() );
		else
			fprintf( stderr, "%s(%d): error: Unexpected end of file\n", m_contextFile.c_str(), m_contextLine );
	}

private:
	CTokenStream*		m_stream;

	inline void SetContext( const CToken& token )
	{
		if ( token.m_id != 0 )
		{
			if ( token.m_file != m_contextFile )
				m_contextFile = token.m_file;

			m_contextLine = token.m_line;
		}
	}

	int					m_numErrors;

	std::string			m_contextFile;
	int					m_contextLine;
};

CLayoutParser::CLayoutParser()
{
}

bool CLayoutParser::ParseGroup( class CLayoutBuilder& builder, class CLayoutBuilderTokens& parsingStream, const bool isMessage )
{
	// name
	std::string groupName;
	if ( !parsingStream.MatchRequiredToken( TOKEN_IDENT, &groupName ) )
		return false;

	// create group
	CLayoutGroup* group = builder.AddGroup( groupName, isMessage );
	if ( !group )
		return false;

	// base definition ?
	if ( parsingStream.MatchToken( ':' ) )
	{
		if ( !isMessage )
		{
			fprintf( stderr, "Structure '%s' cannot derive from another structure\n", group->GetName().c_str() );
			return false;
		}

		std::string baseGroupName;
		if ( !parsingStream.MatchRequiredToken( TOKEN_IDENT, &baseGroupName ) )
			return false;

		builder.SetBaseGroup( group, baseGroupName );
	}

	// group members
	if ( !parsingStream.MatchRequiredToken( '{') )
		return false;

	// parse members
	while ( !parsingStream.End() )
	{
		// end of stream
		if ( parsingStream.MatchToken( '}' ) )
			break;

		// optional flag
		bool isOptional = false;
		if ( parsingStream.MatchToken( TOKEN_OPTIONAL ) )
			isOptional = true;

		// inplace flag
		bool isInplace = false;
		if ( parsingStream.MatchToken( TOKEN_INPLACE ) )
			isInplace = true;

		// attribute, field, group, children or value
		if ( parsingStream.MatchToken( TOKEN_FIELD ) )
		{
			// name
			std::string name;
			if ( !parsingStream.MatchRequiredToken( TOKEN_IDENT, &name ) )
				return false;

			// type declaration
			if ( !parsingStream.MatchRequiredToken( ':' ) )
				return false;

			// type name
			std::string typeName;
			if ( !parsingStream.MatchRequiredToken( TOKEN_IDENT, &typeName ) )
				return false;

			// create field
			auto* field = builder.AddField( group, name, typeName, isOptional, false );
			if ( !field ) 
				return false;

			// default value ?
			if ( parsingStream.MatchToken( '=' ) )
			{
				// the value
				const auto token = parsingStream.Get();
				builder.SetDefaultValue( field, token->m_text );
			}
		}
		else if ( parsingStream.MatchToken( TOKEN_ARRAY ) )
		{
			// group filed name
			std::string name;
			if ( !parsingStream.MatchRequiredToken( TOKEN_IDENT, &name ) )
				return false;

			// type declaration
			if ( !parsingStream.MatchRequiredToken( ':' ) )
				return false;

			// type name
			std::string typeName;
			if ( !parsingStream.MatchRequiredToken( TOKEN_IDENT, &typeName ) )
				return false;

			// create group field
			auto* field = builder.AddField( group, name, typeName, isOptional, true );
			if ( !field ) 
				return false;
		}

		// end of declaration
		if ( !parsingStream.MatchRequiredToken( ';' ) )
			return false;
	}

	// parsed
	return true;
}

bool CLayoutParser::ParseLayout( CLayoutDefinition& outDef, const std::string& fileName, const std::string& txt )
{
	// tokenize code
	CLayoutScriptTokens languageDef;
	CTextParser parser( languageDef );
	std::vector< CToken > tokens;
	if ( !parser.Tokenize( fileName, txt, tokens ) )
		return false;

	// prepare token stream and parser
	CTokenStream rawTokenStream( tokens );
	CLayoutBuilderTokens parsingStream( rawTokenStream );
	CLayoutBuilder builder( parsingStream, &outDef );

	// parse groups
	while ( !parsingStream.End() )
	{
		// group definition
		if ( parsingStream.MatchToken( TOKEN_STRUCT ) )
		{
			if ( !ParseGroup( builder, parsingStream, false ) )
				return false;
		}
		else if ( parsingStream.MatchToken( TOKEN_MESSAGE ) )
		{
			if ( !ParseGroup( builder, parsingStream, true ) )
				return false;
		}
		else
		{
			parsingStream.UnexpectedToken();
			return false;
		}

		// end of declaration
		parsingStream.MatchToken( ';' );
	}

	// errors ?
	if ( parsingStream.GetNumErrors() > 0 )
		return false;

	// no errors detected
	return true;
}
